﻿using System.Collections.Generic;
using System.Web.Http;
using System.Web.Http.Cors;
using System.Net;
using Sustainalytics.Utils.Serilog;
using System;
using MongoDB.Driver;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using System.Threading.Tasks;
using System.Linq;
using MongoDB.Bson;
using System.Text.RegularExpressions;
using Sustainalytics.Entities.SustainableProducts;
using Sustainalytics.Auth.Claims;
using Sustainalytics.Claims;
using Sustainalytics.SustainableProducts.Service.Controllers.Helpers;
using System.Security.Claims;
using Sustainalytics.Utils;

namespace Sustainalytics.SustainableProducts.Service.Controllers
{
    [EnableCors(origins: "*", headers: "*", methods: "*")]
    [RoutePrefix("api/v1/companies")]
    public class ListingController : ApiController
    {
        static IClaimsChecker _claimsChecker;
        private readonly IMongoCollection<CorporateData> _corporateDataCollection;
        private readonly IMongoCollection<SustainableProductsData> _spDataCollection;
        private readonly IMongoCollection<SustainableProductsFramework> _spIndicatorsCollection;
        private readonly string _routeKey;

        public ListingController(
            IClaimsChecker claimsChecker,
            IMongoCollection<CorporateData> corporateDataCollection,
            IMongoCollection<SustainableProductsData> spDataCollection,
            IMongoCollection<SustainableProductsFramework> spIndicatorsCollection)
        {
            try
            {
                _claimsChecker = claimsChecker;
                _corporateDataCollection = corporateDataCollection;
                _spDataCollection = spDataCollection;
                _spIndicatorsCollection = spIndicatorsCollection;
                _routeKey = "sp";
            }
            catch (Exception ex)
            {
                CommonEventSerilog.Log.InitControllerError(ex);
                throw new HttpResponseException(HttpStatusCode.InternalServerError);
            }
        }

        private List<FilterDefinition<SustainableProductsDataUnwinded>> getFilterOptions(SustainableProductsListFilters filters, Guid[] availableCompanies, List<String> claimedIndicators)
        {
            var filtersCompanies = Builders<SustainableProductsDataUnwinded>.Filter;
            var filtersList = new List<FilterDefinition<SustainableProductsDataUnwinded>> { FilterDefinition<SustainableProductsDataUnwinded>.Empty };

            filtersList.Add(Builders<SustainableProductsDataUnwinded>.Filter.In(x => x.Id, availableCompanies));

            if (!filters.CompanyName.IsNullOrEmpty())
                filtersList.Add(filtersCompanies.Regex("CompanyName", new BsonRegularExpression(Regex.Escape(filters.CompanyName.ToLowerInvariant()), "i")));

            filtersList.Add(Builders<SustainableProductsDataUnwinded>.Filter.Where(x => x.Indicators.HasLevelOfInvolvement &&
                                                                                   x.Indicators.ClientFacingProducts.Contains(ClientFacingProduct.GA) &&
                                                                                   claimedIndicators.Contains(x.Indicators.IndicatorSymbol)));
            if (!filters.Indicators.IsNullOrEmpty())
            {
                filtersList.Add(Builders<SustainableProductsDataUnwinded>.Filter.Where(x=>filters.Indicators.Contains(x.Indicators.IndicatorSymbol)));
            }

            if (!filters.Subindustries.IsNullOrEmpty())
            {
                filtersList.Add(Builders<SustainableProductsDataUnwinded>.Filter.Where(x => filters.Subindustries.Contains(x.IndustryId)));
            }

            if (!filters.RangeOwnerships.IsNullOrEmpty())
            {
                filtersList.Add(Builders<SustainableProductsDataUnwinded>.Filter.Where(x => filters.RangeOwnerships.Contains(x.Indicators.LevelOfInvolvement.Text)));
            }

            return filtersList;
        }

        [Route("")]
        [InjectUniverse("universeIds", AccountFeature.SustainableProducts)]
        public async Task<IHttpActionResult> Get(
            [FromUri] SustainableProductsListFilters filterOptions,
            int skip = 0,
            int take = 20,
            bool getCount = false,
            bool orderAscending = true,
            string orderBy = "CompanyName")
        {
            List<int> userUniverseIds = ClaimsPrincipal.Current.Claims.GetUniverseIds(AccountFeature.SustainableProducts);

            var spIndicators = await _spIndicatorsCollection
                        .Find(_ => true)
                        .Project<SustainableProductsFramework>(Builders<SustainableProductsFramework>
                        .Projection
                        .Exclude(x => x.Id)
                        .Include(x => x.Name)
                        .Include(x => x.Categories)
                        .Include(x => x.Symbol))
                        .ToListAsync()
                        .ConfigureAwait(false);

            List<string> spIndicatorsSymbols = spIndicators.Select(x => x.Symbol.ToLower()).ToList();

            List<string> claimedIndicators = _claimsChecker.GetClaimedCompanyIndicators(this, spIndicatorsSymbols, _routeKey);

            if (!claimedIndicators.Any())
            {
                return Unauthorized();
            }

            var corporateFilter = Builders<CorporateData>.Filter;

            var filter = (corporateFilter.Eq(x => x.SolutionsResearchState.ResearchState, ResearchState.ResearchAvailable) &
                corporateFilter.AnyEq(x => x.SolutionsResearchState.ClientFacingProducts, ClientFacingProduct.GA) &
                corporateFilter.AnyIn(x => x.UniverseIds, userUniverseIds));

            var availableCompanies = await _corporateDataCollection
                .Find(filter)
                .Project<CorporateData>(Builders<CorporateData>
                .Projection
                .Include(x => x.Id)
                .Include(x => x.CompanyName)
                .Include(x => x.SolutionsResearchState)
                .Include(x => x.MainExchange)
                .Include(x => x.MainTicker))
                .ToListAsync()
                .ConfigureAwait(false);

            var companiesIds = availableCompanies.Select(x => x.Id).ToArray();

            var filtersForCompanies = getFilterOptions(filterOptions, companiesIds, claimedIndicators);

            if (getCount)
            {
                var companyCount = _spDataCollection.Aggregate()
                .Unwind<SustainableProductsData, SustainableProductsDataUnwinded>(x => x.Indicators)
                .Match(Builders<SustainableProductsDataUnwinded>.Filter.And(filtersForCompanies))
                .Group(x => x.Id, g => new SustainableProductsData()
                {
                    Id = g.First().Id
                }).Group(new BsonDocument
                {
                    {"_id", "_id" },
                    { "Count", new BsonDocument("$sum", 1) }
                });
                
                return Ok(companyCount.Any() ? companyCount.First()["Count"].AsInt32 : 0);
            }

            var cursor = _spDataCollection
                .Aggregate(new AggregateOptions() { Collation = new Collation(locale: "en", caseLevel: true, strength: CollationStrength.Primary) })
                .Unwind<SustainableProductsData,SustainableProductsDataUnwinded>(x=>x.Indicators)
                .Match(Builders<SustainableProductsDataUnwinded>.Filter.And(filtersForCompanies))
                .Group(x => x.Id, g => new SustainableProductsData()
                {
                    Id = g.First().Id,
                    CompanyId = g.First().CompanyId,
                    Indicators = g.Select(x => x.Indicators).ToList(),
                    CompanyName = g.First().CompanyName,
                    CompanyNameLowerCase = g.First().CompanyNameLowerCase,
                    CompanyNameSafeUrl = g.First().CompanyNameSafeUrl,
                    Industry = g.First().Industry
                });
            cursor = cursor.Project<SustainableProductsData>(
                Builders<SustainableProductsData>.Projection
                .Include(x => x.Id)
                .Include(x => x.CompanyName)
                .Include(x => x.CompanyNameSafeUrl)
                .Include(x => x.Indicators)
                .Include(x => x.Industry));

            cursor = cursor.SortBy(s => s.CompanyName);

            var companies = await cursor.Skip(skip).Limit(take).ToListAsync().ConfigureAwait(false);

            var spIndicatorsDictionary = spIndicators.ToDictionary(x => x.Symbol, x => x);

            var computedList = companies.Select(comp =>
            {
                IList<SPIndicator> indicators = new List<SPIndicator>();
                comp.Indicators.ForEach(ind =>
                {
                    if (filterOptions.Indicators.IsNullOrEmpty() || filterOptions.Indicators.Contains(ind.IndicatorSymbol))
                    {
                        var indicatorFramework = spIndicatorsDictionary[ind.IndicatorSymbol];
                        var categoryFramework = indicatorFramework.Categories.FirstOrDefault(cat => cat.Symbol == ind.CategorySymbol);

                        var currentIndicator = indicators.FirstOrDefault(x => x.Symbol == ind.IndicatorSymbol);
                        if (currentIndicator != null)
                        {
                            currentIndicator.Categories.Add(new SPCategory(categoryFramework.Symbol, categoryFramework.CategoryOfInvolvement, ind.LevelOfInvolvement?.Text));
                            currentIndicator.Categories = currentIndicator.Categories.OrderBy(x => x.Symbol).ToList();
                        }
                        else
                        {
                            if (claimedIndicators.Contains(ind.IndicatorSymbol))
                                indicators.Add(new SPIndicator()
                                {
                                    Name = indicatorFramework.Name,
                                    Categories = new List<SPCategory>() { new SPCategory(categoryFramework.Symbol, categoryFramework.CategoryOfInvolvement, ind.LevelOfInvolvement?.Text) },
                                    Symbol = ind.IndicatorSymbol
                                });
                        }
                    }
                });

                return new
                {
                    comp.Id,
                    Name = comp.CompanyName,
                    comp.CompanyNameSafeUrl,
                    Exchange = availableCompanies.FirstOrDefault(x => x.Id == comp.Id).MainExchange,
                    Ticker = availableCompanies.FirstOrDefault(x => x.Id == comp.Id).MainTicker,
                    comp.Industry,
                    Indicators = indicators?.OrderBy(x => x.Name)
                };
            });

            return Ok(computedList);
        }

        public class SPIndicator
        {
            public string Symbol { get; set; }
            public string Name { get; set; }
            public List<SPCategory> Categories { get; set; }
        }

        public class SPCategory
        {
            public SPCategory(string symbol, string name, string levelOfInvolvement)
            {
                Symbol = symbol;
                Name = name;
                LevelOfInvolvement = levelOfInvolvement;
            }

            public string Symbol { get; set; }
            public string Name { get; set; }
            public string LevelOfInvolvement { get; set; }
        }
    }
}
